package tcpEngine;

import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.swing.JOptionPane;

import network.NetworkEngine;


public class TCPNetworkEngine implements NetworkEngine
{
	private int port;
	private String userName;
	private String serverName;
	private List<Connection> connection;
	private List<Connection> removeList;
	private boolean isServer;
	
	public TCPNetworkEngine(int port){
		this.port = port;
		connection = Collections.synchronizedList(new LinkedList<Connection>());
		removeList = new LinkedList<Connection>();
		userName = "userName";
		isServer = false;
	}

	@Override
	public boolean connect(String IP)
	{
		Connection c = new Connection();
		try{
			c.socket = new Socket(IP, port);
			connection.add(c);
		}catch(Exception e){
			e.printStackTrace();
		}
		try{
			c.output = new ObjectOutputStream(c.socket.getOutputStream());
			c.output.flush();
			c.input = new ObjectInputStream(c.socket.getInputStream());
			c.output.writeObject(userName);
			c.output.flush();
			
		}catch(Exception e){
			e.printStackTrace();
		}
		return false;
	}

	@Override
	public boolean createServer(String name)
	{
		//TODO: this function should only be called once, a program should not hold multiple versions of server, need to consider the synchronization problem there
		try {
			isServer = true;
			serverName = name;
			ServerSocket serverSocket = new ServerSocket(port, 10);
			//start to listen to the UDP broadcast
			BroadcastListener listener = new BroadcastListener();
			listener.start();
			
			ServerListener listenerThread = new ServerListener(serverSocket, connection);
			(new Thread(listenerThread)).start();
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		return false;
	}

	@Override
	public boolean createDeepConnectedServer(String name)
	{
		// TODO: ensures deep connection
		return false;
	}

	@Override
	public synchronized List<Object> update() throws IOException
	{
//		System.out.println("in the update function");
		List<Object> rtn = new LinkedList<Object>();
			for(Connection c : connection){
				try{
					Object obj = c.input.readObject();
					if(obj instanceof ConnectionControl && obj == ConnectionControl.STOP){
						removeList.add(c);
					}else {
						rtn.add(obj);
					}
				} catch (SocketTimeoutException  e) {
					
				} catch (IOException e) {
					throw e;
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				}
			}
			for(Connection c : removeList){
				connection.remove(c);
				try {
					c.input.close();
					c.output.close();
					c.socket.close();
					System.out.println("one of the client disconnect");
					throw new IOException();
				} catch (IOException e) {
					throw e;
				}
			}
		removeList.clear();
		return rtn;
	}

	@Override
	public List<ConnectInfo> search()
	{
		//first boardcast UDP to all nearby computers
		BroadcastThread broadcastUDP = new BroadcastThread();
		broadcastUDP.start();
		DatagramSocket server = null;
		
		Set<String> availableServer = new HashSet<String>();
		List<ConnectInfo> availableServers = new ArrayList<ConnectInfo>();
		byte[] buffer = new byte[65507];
		
		try{
			server = new DatagramSocket(Constants.BroadcastResponsePort);
			DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
			server.setSoTimeout(Constants.SearchServerTimeOut);
			
			while(true) {
				server.receive(packet);
				String message = new String(packet.getData(), 0, packet
						.getLength());
				if (message.split(",").length < 2)
					continue;
				String token = message.split(",")[0];
				String serverIP = message.split(",")[1];
				if (token.equals(Constants.BroadcastResponsePrefix) && !availableServer.contains(serverIP)) {
					System.out.println("returned from sever" + serverIP);
					availableServer.add(serverIP);
					availableServers.add(new ConnectInfo(serverIP));
				}
			}
		}catch(Exception e){
			System.out.println("exception occur");
		}
		finally{
			server.close();
		}
		return availableServers;
	}

	@Override
	public boolean disconnect()
	{
//		synchronized(connection){
//			for(Connection c : connection){
//				try{
//					c.output.writeObject(ConnectionControl.STOP);
//					c.output.flush();
//					c.input.close();
//					c.output.close();
//					c.socket.close();
//					connection.remove(c);
//				}catch(Exception e){
//					e.printStackTrace();
//				}
//			}
//		}
		send(ConnectionControl.STOP);
		return false;
	}

	@Override
	public synchronized boolean send(Serializable obj)
	{
//		System.out.println("in the send function");
		// IMPORTANT: Should connection be synchronized here? it seems when synchronized the thread get blocked.
//		synchronized(connection){
			for(Connection c : connection){
				try{
					c.output.writeObject(obj);
					c.output.flush();
				}catch(Exception e){
					e.printStackTrace();
				}
			}
//		}
		return false;
	}
	
	public boolean isRunning(){
		return true;
	}
	
	public boolean isServer(){
		return isServer;
	}
}
